#include "material.h"
#include "result.h"
#include "texture.h"
#include "Utility.h"
#include "MonteCarloUtility.h"
#include <cmath>

namespace mray{

mray::vec3 BaseMaterial::GetColor(const vec2& uv) const
{
	return m_pTex == nullptr ? m_color : m_pTex->GetColor(uv);
}

mray::vec3 LambertMaterial::Shade(const vec3& wi, const vec3& ligColor, const Result& result) const
{
	float theta = wi*result.n;
	if(theta < 0.f) return BLACK;
	return ligColor ^ GetColor(result.t) * (theta * INV_PI);
}

float LambertMaterial::Sample(const Result& result, const vec2& uv, vec3& wi)const
{
	float cos_phi = std::cos(2.0f * PI * uv.x);
	float sin_phi = std::sin(2.0f * PI * uv.x);
	float cos_theta = 1.f - uv.y;
	float sin_theta = std::sqrt(1.0f-cos_theta*cos_theta);
	wi.x = sin_theta * cos_phi;
	wi.y = sin_theta * sin_phi;
	wi.z = cos_theta;

	vec3 xAxis = std::abs(result.n.x)>.1f ? vec3(0.f,1.f,0.f) : vec3(1.f,0.f,0.f);
	xAxis = xAxis % result.n;
	xAxis.normalize();
	vec3 yAxis = result.n % xAxis;
	yAxis.normalize();
	wi = xAxis*wi.x + yAxis*wi.y + result.n*wi.z;
	wi.normalize();
	return (wi*result.n)*INV_PI;
}

mray::vec3 EmissiveMaterial::Shade(const vec3& wi, const vec3& ligColor, const Result& result) const
{
	return GetColor(result.t);
}

float EmissiveMaterial::Sample(const Result& result, const vec2& uv, vec3& wi) const 
{
	return 0.f;
}

mray::vec3 PhongMaterial::Shade(const vec3& wi, const vec3& ligColor, const Result& result) const
{
	float NL = wi*result.n;
	if(NL < 0.f) return BLACK;

	float RV = (result.n*(NL*2.f)-wi) * result.d;
	vec3 diffuse = (GetColor(result.t)^ligColor)*(NL*m_kd);
	if(RV < 0.f) return diffuse;
	else return diffuse + ligColor*(pow(RV, m_exp)*m_ks);
}

float PhongMaterial::Sample(const Result& result, const vec2& uv, vec3& wi) const 
{
	//map cosine sample to phong
	wi = CosineSampleHemisphere(uv.u, uv.v);
	vec3 x = result.n % vec3(0,0,1);
	x.normalize();
	vec3 y = result.n % x;
	y.normalize();

	wi = x*wi.x + y*wi.y + result.n*wi.z;

	return (result.n * wi) * INV_PI;
}

mray::vec3 BlinnMaterial::Shade(const vec3& wi, const vec3& ligColor, const Result& result) const
{
	float NL = wi * result.n;
	if(NL < 0.f) return BLACK;

	vec3 H = (result.d+wi).normalize();
	float NH = H * result.n;
	if(NH < 0.f) return BLACK;

	float D = std::pow(NH, m_exp);

	float NV = result.n * result.d;
	float HV = Clamp(H * result.d, 0.f, 1.f);
	float HL = H * wi;

	float Gm = 2.f*NH*NV/HL;
	float Gs = 2.f*NH*NL/HV;
	float G = Clamp(Min(1.f, Gm, Gs), 0.f, 1.0f);

	return (GetColor(result.t)^ligColor)*(NL*m_kd) + ligColor*(D*G*m_ks);
}

float BlinnMaterial::Sample(const Result& result, const vec2& uv, vec3& wi) const 
{
	float costheta = pow(uv.u, 1.f/(m_exp+1.f));
	float sintheta = sqrt(Max(0.f, 1.f-costheta*costheta));
	float phi = uv.v * 2.f * PI;

	float sinphi = sin(phi);
	vec3 wh(sinphi*costheta, sinphi*sintheta, cos(phi));
	
	vec3 x = result.n % vec3(0,0,1);
	x.normalize();
	vec3 y = result.n % x;
	y.normalize();
	wh = x*wh.x + y*wh.y + result.n*wh.z;

	if(wh * result.n < 0.f) wh = -wh;

	float woDotwh = result.d * wh;
	if(woDotwh <= 0.f) return 0.f;

	wi = -result.d + wh*(2.f*woDotwh);
	if(wi * result.n < 0.f) return 0.f;

	return (m_exp+1.f)*pow(costheta, m_exp)/(2.f*PI*4.f*woDotwh);
}

mray::vec3 MirrorMaterial::Shade(const vec3& wi, const vec3& ligColor, const Result& result) const
{
	return ligColor;
}

float MirrorMaterial::Sample(const Result& result, const vec2& uv, vec3& wi) const
{
	float alpha = result.n * result.d;
	wi = result.n * (alpha * 2.f) - result.d;
	if(m_noise == 0.f) return 1.f;

	vec3 xAxis, yAxis, zAxis(wi);
	float cos_phi = std::cos(2.0f * PI * uv.x);
	float sin_phi = std::sin(2.0f * PI * uv.x);
	float cos_theta = 1.f-uv.y*m_noise;
	float sin_theta = std::sqrt(1.0f-cos_theta*cos_theta);
	wi.x = sin_theta * cos_phi;
	wi.y = sin_theta * sin_phi;
	wi.z = cos_theta;

	xAxis = std::abs(zAxis.x)>.1f ? vec3(0.f,1.f,0.f) : vec3(1.f,0.f,0.f);
	xAxis = xAxis % zAxis;
	xAxis.normalize();
	yAxis = zAxis % xAxis;
	yAxis.normalize();
	wi = xAxis*wi.x + yAxis*wi.y + zAxis*wi.z;
	wi.normalize();
	return 1.f;
}


mray::vec3 TransparentMaterial::Shade(const vec3& wi, const vec3& ligColor, const Result& result) const 
{
	return BLACK;
}

float TransparentMaterial::Sample(const Result& result, const vec2& uv, vec3& wi) const 
{
	return 0.f;
}

}